#include "codec/codec_nt.h"


/// NT codec iterator type.
typedef struct {
    const LSUP_Codec *  codec;      // Codec that generated this iterator.
    LSUP_GraphIterator *gr_it;      // Graph iterator.
    const LSUP_NSMap *  nsm;        // Namespace map.
    size_t              cur;        // Internal cursor.
    LSUP_rc             rc;         // Internal return code.
    char *              str_s,      // Temporary string.
         *              str_p,      // Temporary string.
         *              str_o;      // Temporary string.
} LSUP_NTCodecIterator;



/* * * Codec functions. * * */

static LSUP_rc
term_to_nt (const LSUP_Term *term, const LSUP_NSMap *nsm, char **out_p)
{
    LSUP_rc rc;
    char *out = NULL, *escaped;
    const char *metadata = NULL;
    size_t buf_len;

    char *data = term->data;
    switch (term->type) {
        case LSUP_TERM_NS_IRIREF:
            LSUP_nsmap_normalize_uri (nsm, term->data, &data);
        case LSUP_TERM_IRIREF:
            out = realloc (*out_p, strlen (data) + 3);
            if (UNLIKELY (!out)) return LSUP_MEM_ERR;

            sprintf (out, "<%s>", data);

            if (data != term->data) free (data);
            rc = LSUP_OK;
            break;

        case LSUP_TERM_LITERAL:
            // Calculate string length.
            if (escape_lit (term->data, &escaped) != LSUP_OK)
                return LSUP_ERROR;
            buf_len = strlen (escaped) + 3; // Room for "" and terminator

            if (
                term->datatype != 0
                && term->datatype != LSUP_default_datatype
            ) {
                metadata = term->datatype->data;
                buf_len += strlen (metadata) + 4; // Room for ^^<>
            }

            out = realloc (*out_p, buf_len);
            if (UNLIKELY (!out)) return LSUP_MEM_ERR;

            sprintf (out, "\"%s\"", escaped);
            free (escaped);

            // Add datatype.
            if (metadata)
                out = strcat (strcat (strcat (out, "^^<"), metadata), ">");

            rc = LSUP_OK;

            break;

        case LSUP_TERM_LT_LITERAL:
            // Calculate string length.
            if (escape_lit (term->data, &escaped) != LSUP_OK)
                return LSUP_ERROR;
            buf_len = strlen (escaped) + 3; // Room for "" and terminator

            if (term->lang[0] != '\0') {
                metadata = term->lang;
                buf_len += strlen (metadata) + 1; // Room for @
            }

            out = realloc (*out_p, buf_len);
            if (UNLIKELY (!out)) return LSUP_MEM_ERR;

            sprintf (out, "\"%s\"", escaped);
            free (escaped);

            // Add lang.
            if (metadata) out = strcat (strcat (out, "@"), metadata);

            rc = LSUP_OK;

            break;

        case LSUP_TERM_BNODE:
            out = realloc (*out_p, strlen (term->data) + 3);
            if (UNLIKELY (!out)) return LSUP_MEM_ERR;

            sprintf (out, "_:%s", term->data);
            rc = LSUP_OK;

            break;

        default:
            out = *out_p;  // This is considered garbage.
            rc = LSUP_PARSE_ERR;
    }

    *out_p = out;
    return rc;
}


static void *
gr_to_nt_init (const LSUP_Graph *gr)
{
    LSUP_NTCodecIterator *it;
    CALLOC_GUARD (it, NULL);

    it->codec = &nt_codec;
    it->gr_it = LSUP_graph_lookup(gr, NULL, NULL, NULL, &it->cur);
    it->nsm = LSUP_graph_namespace (gr);

    return it;
}


static LSUP_rc
gr_to_nt_iter (void *h, char **res) {
    LSUP_NTCodecIterator *it = h;
    LSUP_Triple *spo = NULL;
    LSUP_rc rc = LSUP_graph_iter_next (it->gr_it, &spo);
    if (rc != LSUP_OK) goto finally;

    term_to_nt (spo->s, it->nsm, &it->str_s);
    term_to_nt (spo->p, it->nsm, &it->str_p);
    term_to_nt (spo->o, it->nsm, &it->str_o);
    LSUP_triple_free (spo);

    // 3 term separators + dot + newline + terminal = 6
    char *tmp = realloc (
            *res, strlen (it->str_s) + strlen (it->str_p)
            + strlen (it->str_o) + 6);
    if (UNLIKELY (!tmp)) {
        *res = NULL;
        rc = LSUP_MEM_ERR;
        goto finally;
    }

    sprintf (tmp, "%s %s %s .\n", it->str_s, it->str_p, it->str_o);
    *res = tmp;

    it->cur++;

finally:
    return rc;
}


static void
gr_to_nt_done (void *h)
{
    LSUP_NTCodecIterator *it = h;
    LSUP_graph_iter_free (it->gr_it);
    free (it->str_s);
    free (it->str_p);
    free (it->str_o);
    free (it);
}


const LSUP_Codec nt_codec = {
    .name               = "N-Triples",
    .mimetype           = "application/n-triples",
    .extension          = "nt",

    .encode_term        = term_to_nt,

    .encode_graph_init  = gr_to_nt_init,
    .encode_graph_iter  = gr_to_nt_iter,
    .encode_graph_done  = gr_to_nt_done,

    .decode_term        = LSUP_nt_parse_term,
    .decode_graph       = LSUP_nt_parse_doc,
};
